#include <GL/glut.h>
#include <list>
#include <math.h>
#include <iostream>
using namespace std;
#include <string>
#include <stdlib.h>
#include "list"

#include "Universe.h"
#include "Definitions.h"
#include "CircleMass.h"
using namespace ms;

Universe universe;
std::list<CircleMass> * masterListPtr;

void renderScene(void);
void drawCircle(float x, float y, float radius, float red, float green, float blue);
void doPhysics(int val);
void handleKeyboard(unsigned char key, int x, int y);
void zoomOrtho(float zoom);
void evaluate(string s);
void addCircleMass();
void removeCircleMass();
void showMasses();
void setp();
void sett();
void setc();
void help();

float zoom = 1.0;
float timeScale = .0005;
float cameraX = 0;
float cameraY = 0;
bool contPausedState; // paused state to use when we return from the console (global so setp command can access it)

int main(int argc, char **argv) {
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
	const unsigned int width = 1000;
	const unsigned int height = 1000;
	glutInitWindowSize(width, height);
	glutCreateWindow("Mass Simulator");
	zoomOrtho(1.1); // fixes some stuff for some reason

	masterListPtr = universe.getMasterListPtr();

	//universe.addCircleMass(0, 0, 1e20, 1.98892e30, 0, 0); // sun
	//universe.addCircleMass(1.49597e11, 0, 1e20, 5.9742e24, 0, 29783); // earth (radius is waaay off)
	universe.addCircleMass(-.5, 0, .05, 1e11, 0, 1);
	universe.addCircleMass(.5, 0, .05, 1e11, 0, -1);
	//universe.addCircleMass(0, .5, .05, 1e11, 0, 0);
	//universe.addCircleMass(.7, 0, .1, 1e11, 0, 0);
	//universe.addCircleMass(-.7, 0, .1, 1e11, 0, 0);

	glutDisplayFunc(renderScene);
	glutKeyboardFunc(handleKeyboard);
	glutTimerFunc(0, doPhysics, 0); // Update the physics every millisec.
	glutMainLoop();
}

void renderScene(void) {
	glClear(GL_COLOR_BUFFER_BIT);

	std::list<CircleMass>::iterator i;
	for(i = masterListPtr -> begin(); i != masterListPtr -> end(); ++i) {
		drawCircle(i -> position.x, i -> position.y, i -> radius, 0.0, 0.7, 0.7);
	}   
	glFlush();
	glutSwapBuffers();
}

void drawCircle(float x, float y, float radius, float red, float green, float blue) {
	int segments; // number of sides to the polygon
#ifdef HIGHQUALITY
	segments = 40; 
#else
	segments = 20;
#endif

	real points[segments][2]; // (x, y) coordinates of each point of the circle
	real angleIncrement = (2 * M_PI) / segments; // the angle of each triangle, how much to increment
	real theta; // temporary angle counter
	glColor3f(red, green, blue);

	glBegin(GL_POLYGON);
	for (theta = 0; theta < 2 * M_PI; theta += angleIncrement) {
		// x = a + radius * cos(theta)
		// y = b + radius * sin(theta)
		glVertex3f(x + radius * cos(theta), y + radius * sin(theta), 0.0);
	}
	glEnd();
}

void doPhysics(int val) {
	universe.update(timeScale);
	glutPostRedisplay();
	//showMasses();
	glutTimerFunc(1,doPhysics,0);
}

void zoomOrtho(float zoom) {
	glOrtho(-zoom, zoom, -zoom, zoom, 0, 1000);
}

void handleKeyboard(unsigned char key, int x, int y) {
	switch (key) {
		case '+': // zoom in
			zoomOrtho(.9);
			break;
		case '-': // zoom out
			zoomOrtho(1.1);
			//resetOrtho();
			break;
		case '.': // speed up
			timeScale += .00001;
			cout << "timeScale = " << timeScale << endl;
			break;
		case ',': // slow down
			timeScale -= .00001;
			cout << "timeScale = " << timeScale << endl;
			break;
		case 'a': // move camera left
			gluLookAt(-.5, 0, 1, -.5, 0, 0, 0, 1, 0);
			break;
		case 'd': // move camera right
			gluLookAt(.5, 0, 1, .5, 0, 0, 0, 1, 0);
			break;
		case 'w': // move up
			gluLookAt(0, .5, 1, 0, .5, 0, 0, 1, 0);
			break;
		case 's': // move up
			gluLookAt(0, -.5, 1, 0, -.5, 0, 0, 1, 0);
			break;
		case ':': // get user input
			contPausedState = universe.getPaused();
			universe.setPaused(true);
			glutHideWindow();
			string s;
			cout << "Enter :help for list of commands" << endl;
			cout << ": ";
			cin >> s;
			evaluate(s);
			glutShowWindow();
			universe.setPaused(contPausedState);
			break;
	}
}

void evaluate(string s) { // evaluates user's commands
	if (s == "exit") {
		exit(0);
	} else if (s == "add") {
		addCircleMass();
	} else if (s == "ls") {
		showMasses();
	} else if (s == "rm") {
		removeCircleMass();
	} else if (s == "help") {
		help();
	} else if (s == "setp") {
		setp();
	} else if (s == "sett") {
		sett();
	} else if (s == "setc") {
		setc();
	} else {
		cout << "ERROR: Command not recognized!" << endl;
	}
	cout << endl;

}

void addCircleMass() {
	real x, y, mass, radius, vx, vy;
	cout << "x     : "; cin >> x;
	cout << "y     : "; cin >> y;
	cout << "radius: "; cin >> radius;
	cout << "mass  : "; cin >> mass;
	cout << "vx    : "; cin >> vx;
	cout << "vy    : "; cin >> vy;

	universe.addCircleMass(x, y, radius, mass, vx, vy);
	cout << endl;
}

void removeCircleMass() {
	showMasses();
	cout << "Which object index to remove? ";
	int rem;
	cin >> rem;
	std::list<CircleMass> * masterListPtr; // a pointer to universe's masterList
	masterListPtr = universe.getMasterListPtr();
	if ((rem + 1 > masterListPtr -> size()) || (rem < 0)) {
		cout << "Out of bounds!" << endl;
		return;
	}
	std::list<CircleMass>::iterator i; // used to iterate through ptrMasterList
	int count;
	for (i = masterListPtr -> begin(), count = 0; count < rem; ++count, ++i);
	masterListPtr -> erase(i);
	cout << endl;
}

void showMasses() {
	std::list<CircleMass> * masterListPtr; // a pointer to universe's masterList
	masterListPtr = universe.getMasterListPtr();
	int count;
	std::list<CircleMass>::iterator i; // used to iterate through ptrMasterList

	cout << "paused is set to ";
	if (contPausedState) {
		cout << "true" << endl;
	} else {
		cout << "false" << endl;
	}

	cout << "timeScale is set to " << timeScale << endl;

	if (masterListPtr -> size() > 0) {
		cout << "object\t|\t(x, y)\t\t\t|\tmass\t\t|\tVx\t\t|\tVy" << endl;
		cout << "--------------------------------------------------------------------------------" << endl;
		for (i = masterListPtr -> begin(), count = 0; i != masterListPtr -> end(); ++i, ++count) {
			cout << count << "\t|\t(" << i -> position.x << ", " << i -> position.y << ")\t\t\t|\t" << i -> mass << "\t\t|\t" << i -> getVelocity().x << "\t\t|\t" << i -> getVelocity().y << endl;
		}
	}
	cout << endl;

}

void setp() { // asks the user if they want paused set when they return from the console
	cout << "Would you like the simulation to be paused?" << endl 
		<< "(Note: if no, it will be paused whenever you are in the console)" << endl
		<< "1. Yes" << endl << "2. No" << endl << "Selection: ";
	int selection = 1;
	cin >> selection;
	if (selection == 1) {
		cout << "Setting paused to on" << endl;
		contPausedState = true;
	} else if (selection == 2) {
		cout << "Setting paused to off" << endl;
		contPausedState = false;
	} else {
		cout << "ERROR: Invalid number, defaulting to paused on" << endl;
		contPausedState = true;
	}
}


void setc() { // asks the user if they want collision enabled
	cout << "Would you like to enable collision detection?" << endl 
		<< "1. Yes" << endl << "2. No" << endl << "Selection: ";
	int selection = 1;
	cin >> selection;
	if (selection == 1) {
		cout << "Turning on collision" << endl;
		universe.setCollision(true);
	} else if (selection == 2) {
		cout << "Turning off collision" << endl;
		universe.setCollision(false);
	} else {
		cout << "ERROR: Invalid number, leaving collision setting unchanged" << endl;
	}
}

void sett() { // prompts the user for the time scale
	cout << "Current timeScale:" << timeScale << endl;
	cout << "New timeScale?: ";
	cin >> timeScale;
}

void help() {
	cout
		<< "exit : quit this program" << endl
		<< "add  : add a new circular mass" << endl
		<< "setp : set if scene is paused" << endl
		<< "sett : set the timeScale" << endl
		<< "setc : set if collision is enabled" << endl
		<< "rm   : remove a mass" << endl
		<< "ls   : list all masses and relevant information" << endl
		<< "help : show this menu" << endl;
	cout << endl;
}
